package main

import (
	"fmt"
	"sync"
	"time"
)

/*
但Mutex在大量并发的情况下，会造成锁等待，对性能的影响比较大。如果某个读操作的协程加了锁，其他的协程没必要处于等待状态，
可以并发地访问共享变量，这样能让读操作并行，提高读性能。如果我们可以明确区分reader和writer的协程场景，
且是大量的并发读、少量的并发写，有强烈的性能需要，我们就可以考虑使用读写锁RWMutex替换Mutex
一、读写锁：
这种锁在某一时刻能由任意数量的reader持有，或者被一个wrtier持有，是经典的单写多读模型，读可以并发，写不能，读写也不能同时存在
二、使用主要遵循以下规则 ：
读写锁的读锁可以重入，在已经有读锁的情况下，可以任意加读锁。
在读锁没有全部解锁的情况下，写操作会阻塞直到所有读锁解锁。
写锁定的情况下，其他协程的读写都会被阻塞，直到写锁解锁。
三、Go语言的读写锁方法主要有下面这种：
3.1、Lock/Unlock：针对写操作--写锁和解锁
	不管锁是被reader还是writer持有，这个Lock方法会一直阻塞，Unlock用来释放锁的方法。
3.2、RLock/RUnlock：针对读操作--读锁和解锁
	当锁被reader所有的时候，RLock会直接返回，当锁已经被writer所有，RLock会一直阻塞，直到能获取锁，
	否则就直接返回，RUnlock用来释放锁的方法。
注意：写独占，读共享，写锁优先级高
所以: 锁的本质其实是将并行的代码串行，使用lock肯定影响性能，设计锁的时候尽量保持并行，可以将锁分组，如读锁组，写锁组

*/

func main() {
	var wg sync.WaitGroup   //定义协程等待
	var rwlock sync.RWMutex // 初始化声明读写锁

	wg.Add(12) //增加协程监视器
	//写协程
	go func() {
		defer wg.Done()       //关闭监视器
		rwlock.Lock()         //获取写锁
		defer rwlock.Unlock() //一般通过defer延迟函数来解锁
		fmt.Println("write1 lock")
		time.Sleep(5 * time.Second) //由于没有学习通信，这个通过time来保证协程的执行顺序，通过打印的时间间隔判断写锁是否阻碍读锁
	}()

	//读协程,并通过循环实现并发读
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			for { //for死循环保证写锁的一直执行
				rwlock.RLock() //获取读锁
				time.Sleep(500 * time.Millisecond)
				//defer rwlock.RUnlock() //一般通过defer延迟函数来解锁
				fmt.Println("read lock", i)
				rwlock.RUnlock()
			}
		}()
	}

	//写协程
	go func() {
		defer wg.Done()       //关闭监视器
		rwlock.Lock()         //获取写锁
		defer rwlock.Unlock() //一般通过defer延迟函数来解锁
		fmt.Println("write2 lock")
		time.Sleep(5 * time.Second) //由于没有学习通信，这个通过time来保证协程的执行顺序，通过打印的时间间隔判断写锁是否阻碍读锁
	}()

	wg.Wait() //等待协程结束

	/*
		从如上结果可以看到，首先第一个发生写锁的时候，隔了5秒后，写锁解锁后才出现读锁，并同时出现10个读锁，
		当10个死循环读锁第一次解锁后,出现第二个写锁，也是在写锁持续五秒后，10个读锁又才出现
		结论：写锁出现时，读锁不能枷锁，当所有写锁解锁后才能加锁
			 读锁出现时，写锁不能枷锁，当所有写锁解锁后才能加锁
			 读锁可以并发出现
	*/

}
